<template>
	<view class="v-scroll-list-container">
		<scroll-view
			class="v-scroll-list"
			scroll-y
			:scroll-into-view="activeId"
			:scroll-with-animation="true"
			:show-scrollbar="false"
			:style="{
				height: scrollHeight + 'rpx'
			}"
			@scroll="handleScroll"
		>
			<view
				v-for="(item, index) in options"
				:key="index"
				:id="`item_${item.value}`"
				class="v-scroll-list-item"
				:class="{ 'v-scroll-list-item-active': item.value === selectedValue }"
				:style="{ height: itemHeight + 'rpx' }"
			>
				{{ item.label }}
			</view>
		</scroll-view>
		<view class="v-scroll-list-mask" :style="maskStyle"></view>
	</view>
</template>

<script lang="ts" setup>
import { ref, computed, watch, nextTick, inject } from 'vue';

interface ScrollListItem {
	value: any;
	label: string;
}

/**
 * v-scroll-list
 * @description 滚动选择列表
 * @property {Array} options 列表数据
 * @property {String|Number} value 当前选中值
 * @property {Number} scrollHeight 滚动区域高度
 * @property {Number} itemHeight 列表项高度
 * @event {Function} update:value 选中值变化时触发
 * @example
 * <v-scroll-list :options="options" v-model="value" :scroll-height="500" :item-height="80"></v-scroll-list>
 */
const props = defineProps({
	options: {
		type: Array as () => ScrollListItem[],
		required: true
	},
	value: {
		type: [String, Number],
		default: ''
	},
	scrollHeight: {
		type: Number,
		default: 500
	},
	itemHeight: {
		type: Number,
		default: 80
	}
});

const emit = defineEmits(['update:value']);

const config = inject<any>('config');
const selectedValue = ref(props.value);
const activeId = ref('');
const itemPositions = ref<number[]>([]);
let currentIndex = 0;
let currentTop = 0;
let isProgrammaticScroll = ref(true);

const maskHeight = computed(() => (props.scrollHeight - props.itemHeight) / 2);
const maskStyle = computed(() => ({
	top: `0`,
	height: `${props.itemHeight}rpx`
}));

const initPositions = async () => {
	await nextTick();
	activeId.value = `item_${selectedValue.value}`;
	currentIndex = props.options.findIndex((item) => item.value === selectedValue.value) - 1;
};

const handleScroll = (event: any) => {
	if (isProgrammaticScroll.value) {
		const operation = event.detail.deltaY;

		if (operation < 0) {
			currentIndex++;
			if (operation < -5) {
				currentIndex += Math.trunc(Math.abs(operation) % 3);
			}
		} else {
			currentIndex--;
			if (operation > 5) {
				currentIndex -= Math.trunc(Math.abs(operation) % 3);
			}
		}

		if (currentIndex < 0) {
			currentIndex = props.options.length - 1;
		} else if (currentIndex >= props.options.length) {
			currentIndex = 0;
		}

		selectedValue.value = props.options[currentIndex].value;
		activeId.value = `item_${selectedValue.value}`;
		emit('update:value', selectedValue.value);
		isProgrammaticScroll.value = false;
	} else {
		isProgrammaticScroll.value = true;
	}
};

// 选中项变化时自动居中
watch(selectedValue, (newVal) => {
	if (isProgrammaticScroll.value) {
		activeId.value = `item_${currentIndex}`;
		isProgrammaticScroll.value = false;
	}
});

watch(() => props.options, initPositions, { deep: true });

initPositions();
</script>

<style lang="scss" scoped>
.v-scroll-list-container {
	position: relative;
	height: 100%;
}

.v-scroll-list {
	box-sizing: content-box;
}

.v-scroll-list-item {
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: v-bind('config.fontSize.mediumText');
	color: v-bind('config.fontColor.mainText');
	transition: all 0.2s ease;
}
.v-scroll-list-item:last-child {
	padding-bottom: 300rpx;
}
.v-scroll-list-item-active {
	color: v-bind('config.fontColor.default');
	font-weight: bold;
	transform: scale(1.1);
}

.v-scroll-list-mask {
	position: absolute;
	left: 0;
	right: 0;
	pointer-events: none;
	background: rgba(200, 200, 200, 0.1);
	border-top: 1rpx solid v-bind('config.border.color');
	border-bottom: 1rpx solid v-bind('config.border.color');
}
</style>
